using System;
using System.IO;
using System.Text;
using ICommonLibrary.Cocurrent;
using ICommonLibrary.Resource;
using NLog;

namespace ICommonLibrary {

    public class IOUtil {

        public static bool MoveQuietly(string file, string destination) {
            return MoveQuietly(new FileInfo(file), destination);
        }

        public static bool MoveQuietly(FileInfo file, string destination) {
            try {
                var destFile = new FileInfo(destination);
                if (destFile.Directory != null) {
                    destFile.Directory.Create();
                }
                file.MoveTo(destination);
                return true;
            }
            catch (Exception e) {
                LogManager.GetCurrentClassLogger().ErrorException("Unable to move " + file.FullName + " to " + destination + ": " + e.Message, e);
                return false;
            }
        }

        public static void CopyStream(Stream input, Stream output) {
            var buffer = new byte[32768];
            while (true) {
                int read = input.Read(buffer, 0, buffer.Length);
                if (read <= 0)
                    return;
                output.Write(buffer, 0, read);
            }
        }

        /// <summary>
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. 
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        public static byte[] Read(Stream stream) {
            return Read(stream, 0);
        }

        /// <summary>
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. 
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="initialLength">The initial buffer length</param>
        public static byte[] Read(Stream stream, int initialLength) {

            if (initialLength < 1) {
                initialLength = 32768; // 32k
            }

            var buffer = new byte[initialLength];
            int read = 0;

            int chunk;
            if (stream != null)
                while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0) {
                    read += chunk;

                    if (read == buffer.Length) {
                        int nextByte = stream.ReadByte();

                        // End of stream? If so, we're done
                        if (nextByte == -1) {
                            return buffer;
                        }

                        // Nope. Resize the buffer, put in the byte we've just
                        // read, and continue
                        var newBuffer = new byte[buffer.Length * 2];
                        Array.Copy(buffer, newBuffer, buffer.Length);
                        newBuffer[read] = (byte)nextByte;
                        buffer = newBuffer;
                        read++;
                    }
                }

            // Buffer is now too big. Shrink it.
            var ret = new byte[read];
            Array.Copy(buffer, ret, read);
            return ret;
        }

        public static byte[] ReadAndClose(IResource resource) {
            try {
                return ReadAndClose(resource.GetStream());
            }
            catch(Exception e) {
                throw new IOException("Can not read " + resource.Description, e);
            }
        }

        public static byte[] ReadAndClose(Stream input) {
            using (input) {
                return Read(input);
            }
        }

        public static byte[] ReadBinaryFile(FileInfo fileInfo) {
            using (var stream = fileInfo.OpenRead()) {
                return ReadAndClose(stream);
            }
        }

        public static byte[] ReadBinaryFile(FileInfo fileInfo, int maxAttempts, TimeSpan attemptInterval, out int attempts) {
            Exception lastError = null;

            attempts = 1;
            for (; attempts <= maxAttempts; attempts++) {
                try {
                    using (var stream = fileInfo.OpenRead()) {
                        return ReadAndClose(stream);
                    }
                }
                catch (Exception e) {
                    lastError = e;
                    ThreadUtil.Sleep(attemptInterval);
                }
            }
            throw lastError ?? new IOException("Couldn't read " + fileInfo.FullName);
        }

        public static string ReadTextFile(FileInfo fileInfo) {
            using (var stream = fileInfo.OpenText()) {
                return stream.ReadToEnd();
            }
        }

        public static string ReadTextFile(FileInfo fileInfo, int maxAttempts, TimeSpan attemptInterval, out int attempts) {
            Exception lastError = null;

            attempts = 1;
            for (; attempts <= maxAttempts; attempts++) {
                try {
                    using (var stream = fileInfo.OpenText()) {
                        return stream.ReadToEnd();
                    }
                }
                catch (Exception e) {
                    lastError = e;
                    ThreadUtil.Sleep(attemptInterval);
                }
            }
            throw lastError ?? new IOException("Couldn't read " + fileInfo.FullName);
        }

        public static string ReadStringAndClose(Stream input) {
            using (input) {
                using (var sr = new StreamReader(input)) {
                    return sr.ReadToEnd();
                }
            }
        }

        public static string ReadStringAndClose(TextReader input) {
            using (input) {
                return input.ReadToEnd();
            }
        }

        public static void WriteTextFile(FileInfo file, string text) {
            using (var stream = file.OpenWrite()) {
                byte[] bytes = Encoding.UTF8.GetBytes(text);
                stream.Write(bytes, 0, bytes.Length);
            }
        }

        public static void WriteBinaryFile(FileInfo file, byte[] bytes) {
            using (var stream = file.OpenWrite()) {
                stream.Write(bytes, 0, bytes.Length);
            }
        }

    }
}